﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace Red.GameData.Raw
{
    /// <summary>
    ///  Generic utility class for raw data manipulation, a semi-binding point for the data source and reflection
    /// </summary>
    public class Utils
    {
        /// <summary>
        /// GUID bullshit - convert from C# Guid (4-2-2-8) into our Guids (4-4-4-4) format
        /// </summary>
        /// <param name="g">The incoming Guid</param>
        /// <returns>The 4-4-4-4 guid or an empty string if the incoming guid was empty</returns>
        public static string FormatGuid(Guid g)
        {
            if (g == Guid.Empty)
                return "";

            var data = g.ToByteArray();

            var a = BitConverter.ToUInt32(data, 0);
            var b = BitConverter.ToUInt32(data, 4);
            var c = BitConverter.ToUInt32(data, 8);
            var d = BitConverter.ToUInt32(data, 12);

            return "[" + String.Format("{0:X8}-{1:X8}-{2:X8}-{3:X8}", a, b, c, d) + "]";
        }

        /// <summary>
        /// GUID bullshit - convert from C# Guid (4-2-2-8) into our Guids (4-4-4-4) for a VALUE format
        /// </summary>
        /// <param name="g">The incoming Guid</param>
        /// <returns>The 4-4-4-4 guid or an empty string if the incoming guid was empty</returns>
        public static string FormatValueGuid(Guid g)
        {
            if (g == Guid.Empty)
                return "";

            var data = g.ToByteArray();

            var a = BitConverter.ToUInt32(data, 0);
            var b = BitConverter.ToUInt32(data, 4);
            var c = BitConverter.ToUInt32(data, 8);
            var d = BitConverter.ToUInt32(data, 12);

            return "{" + String.Format("{0:X8}-{1:X8}-{2:X8}-{3:X8}", a, b, c, d) + "}";
        }


        /// <summary>
        /// Try to parse an our 4-4-4-4 GUID value from the incoming text
        /// </summary>
        /// <param name="text">Soruce text we are trying to parse the guid from. NOTE: only the characters between [ and ] are interpretted. There's no whitespace skipping.</param>
        /// <param name="outGuid">Output guid in case the conversion was successfull</param>
        /// <returns>True on success, false if the text did not represent a proper 4-4-4-4 GUID</returns>
        public static bool ParseGuid(string text, ref Guid outGuid)
        {
            // length check first
            if ( text.Length < 2+8+1+8+1+8+1+8 )
                return false;

            char[] separators = { '-' };
            string[] vals = text.Substring(1, text.Length - 2).Split(separators); // extract the inner number
            if (vals.Length == 4)
            {
                UInt32 a = 0, b = 0, c = 0, d = 0;
                if (UInt32.TryParse(vals[0], System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture, out a) &&
                    UInt32.TryParse(vals[1], System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture, out b) &&
                    UInt32.TryParse(vals[2], System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture, out c) &&
                    UInt32.TryParse(vals[3], System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture, out d))
                {
                    Byte[] data = new Byte[16];
                    BitConverter.GetBytes(a).CopyTo(data, 0);
                    BitConverter.GetBytes(b).CopyTo(data, 4);
                    BitConverter.GetBytes(c).CopyTo(data, 8);
                    BitConverter.GetBytes(d).CopyTo(data, 12);
                    outGuid = new Guid(data);
                    return true;
                }
            }

            return false;
        }

        //------------------------------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// XML parsing shit - extract integer from the node's attribute
        /// </summary>
        /// <param name="node">The node</param>
        /// <param name="name">The attribute's name</param>
        /// <param name="defaultValue">In case the value is NOT parsed set the return to this default</param>
        /// <returns>The extracted value or an empty string if attribute was not found</returns>
        public static int Attr(XmlNode node, string name, int defaultValue)
        {
            int val = defaultValue;

            var attrNode = node.Attributes[name];
            if (null != attrNode)
            {
                var attr = attrNode.InnerText;
                int.TryParse(attr, out val);
            }

            return val;
        }

        /// <summary>
        /// XML parsing shit - extracts string from the node's attribute
        /// In case there's no attribute the empty string is returned
        /// </summary>
        /// <param name="node">The node</param>
        /// <param name="name">The attribute's name</param>
        /// <returns>The extracted value or an empty string if attribute was not found</returns>
        public static string Attr(XmlNode node, string name)
        {
            var attrNode = node.Attributes[name];
            if (null != attrNode)
            {
                var attr = attrNode.InnerText;
                return attr;
            }

            return "";
        }

        /// <summary>
        /// XML parsing shit - extracts string from the node's attribute
        /// In case there's no attribute the NULL value is returned, empty string is only returned if the actual value was empty
        /// </summary>
        /// <param name="node">The node</param>
        /// <param name="name">The attribute's name</param>
        /// <returns>The extracted value or NULL if attribute was not found</returns>
        public static string AttrMaybe(XmlNode node, string name)
        {
            var attrNode = node.Attributes[name];
            if (null != attrNode)
            {
                return attrNode.InnerText;
            }

            return null;
        }

        /// <summary>
        /// XML parsing shit - Parse a 4-4-4-4 ID from the node's attribute
        /// </summary>
        /// <param name="node">The node</param>
        /// <param name="name">The attribute's name</param>
        /// <returns>The extracted guid</returns>
        public static Guid AttrID(XmlNode node, string name)
        {
            var attrNode = node.Attributes[name];
            if (null != attrNode)
            {
                var attr = attrNode.InnerText;
                if (attr != "")
                {
                    Guid ret = Guid.Empty;
                    if (ParseGuid(attr, ref ret))
                        return ret;
                }
            }

            return Guid.Empty;
        }

        /// <summary>
        ///  XML parsing shit - Parse a boolean from the node's attribute
        /// </summary>
        /// <param name="node">The node</param>
        /// <param name="name">The attribute's name</param>
        /// <param name="defaultValue">Default value to use in case there's no attribute or we were not able to parse it's value</param>
        /// <returns>The extracted boolean value or the default value if there were no attribute or we were not able to parse it's value</returns>
        public static bool Attr(XmlNode node, string name, bool defaultValue)
        {
            bool val = defaultValue;

            var attrNode = node.Attributes[name];
            if (null != attrNode)
            {
                var attr = attrNode.InnerText;
                if (attr == "true")
                {
                    val = true;
                }
                else if (attr == "false")
                {
                    val = false;
                }
                else
                {
                    int intVal = 0;
                    if (int.TryParse(attr, out intVal))
                        val = (intVal != 0);
                }
            }

            return val;
        }

        //------------------------------------------------------------------------------------------------------------------------------------------

        /// <summary>
        /// Given an object find the name of the class it's created from
        /// NOTE: since the class
        /// </summary>
        /// <param name="data"></param>
        /// <param name="obj"></param>
        /// <returns></returns>

        public static string GetObjectClassName( Raw.DataObject obj )
        {
            while ( obj != null )
            {
                // well, this object has a valid class entry speicified
                if ( obj.ClassName != "" )
                    return obj.ClassName;

                // go to base object
                obj = obj.File.Collection.Find( obj.BaseID );
            }

            // no class entry found for given object
            return "";
        }

        /// <summary>
        /// Given an object find the reflection class it's created from
        /// </summary>
        /// <param name="reflection">The reference to the global reflection tables</param>
        /// <param name="obj">Object we are interested in</param>
        /// <returns>Reflection class of the object or NULL if something is seriously fucked</returns>
        public static Raw.ReflectionClass GetObjectClass( Raw.ReflectionData reflection, Raw.DataObject obj )
        {
            // well, get the string name first...
            var className = GetObjectClassName( obj );
            if ( className == "" )
                return null;

            // ... and run it though the reflection tables
            // seriously, it's here ONLY because I'm lazy typer
            return reflection.FindClass(className);
        }

        /// <summary>
        /// Given an object find it's KEY NAME
        /// This requires finding the class first, then looking up the name of the "key" property and finally, looking up the key name
        /// NOTE: the "key" name should NEVER be derived so we are not checking the base classes for the value of the property
        /// NOTE: the returned value will be an empty string for nameless objects so it's not the best method to use for UI applications
        /// </summary>
        /// <param name="reflection">The reference to the global reflection tables</param>
        /// <param name="obj">The object for which we want to get the name</param>
        /// <returns>The name of the object or an empty string for nameless objects</returns>
        public static string GetObjectName(Raw.ReflectionData reflection, Raw.DataObject obj)
        {
            // we need the object's class first
            var objectClass = GetObjectClass(reflection, obj);
            if (objectClass != null)
            {
                // having the class - get the class's key property name
                var keyName = objectClass.KeyName;
                if (keyName != "")
                {
                    // lookup the value for the key property
                    // NOTE: this assumes that there's only one value for the key property
                    return obj.GetPropertyValueSingle(keyName);
                }
            }

            // well, there's no way to get the name of the object
            return "";
        }

        /// <summary>
        /// Get a SAFE (displayable) name for an object
        /// Similarly to GetObjectName it's using the key property to resolve the object's name.
        /// In case the object is nameless a more raw, system name is constructed so it can be displayed somehow.
        /// </summary>
        /// <param name="reflection">The reference to the global reflection tables</param>
        /// <param name="obj">The object for which we want to get the name</param>
        /// <returns>The safe name of object or "NULL" if the object was not there</returns>
        public static string GetObjectNameSafe(Raw.ReflectionData reflection, Raw.DataObject obj)
        {
            // no object case
            if (obj == null)
                return "NULL";

            // we need to get the object's class any way
            var objectClass = GetObjectClass(reflection, obj);
            if (objectClass == null)
                return "Object: " + FormatGuid(obj.ID); // no object class, use generic name

            // if there's a key property resolve the object's name
            var keyName = objectClass.KeyName;
            if (keyName == "") // no key property, use system name
                return objectClass.Name + ": " + FormatGuid(obj.ID);

            // finally, do similarly thing as for the normal GetObjectName function
            var name = obj.GetPropertyValueSingle(keyName);
            return objectClass.Name + ": '" + name + "'";
        }
    }

    //------------------------------------------------------------------------------------------------------------------------------------------

    /// <summary>
    /// Generic error/progress reporting interface for operations that may fail inside in a complex way and/or are taking some time to finish
    /// </summary>
    public interface IGenericFileErrorReporter
    {
        /// <summary>
        /// Whenever a new file is opened this method is called to track the file
        /// </summary>
        /// <param name="filePath">Path to the file that was just opened</param>
        void StartFile(string filePath);

        /// <summary>
        /// Whenever and error occurs this method is called with description of the problem
        /// </summary>
        /// <param name="error"></param>
        void Error(string error);
        
        /// <summary>
        /// Global progress tracking for the whole operation
        /// </summary>
        /// <param name="current">Current element being processed</param>
        /// <param name="total">Total number of elements to process</param>
        void TotalProgress(int current, int total);

        /// <summary>
        /// In-file progress tracking
        /// </summary>
        /// <param name="current">Current element in a file that is being processed</param>
        /// <param name="total">Total number of elements in a file that are there to process</param>
        void FileProggres(int current, int total);
    }

    //------------------------------------------------------------------------------------------------------------------------------------------

}
